package idc

import (
	"go.uber.org/zap"
	"sync"
	"sync/atomic"
	"time"
	"wzgames/pkg/logger"
)

type IdBiz struct {
	bizid int64
	wk    *IdWorker

	segment *IdSegment // id segment
	nextSeg *IdSegment // id segment

	currentID uint64

	sync.RWMutex
	queryMux sync.Mutex
}

func newIdBiz(bizid int64, wk *IdWorker) *IdBiz {
	b := &IdBiz{
		bizid: bizid,
		wk:    wk,
	}
	s := b.getNextSegment()
	b.Lock()
	if s == nil {
		s = &IdSegment{Maxid: 0, Idstep: 0}
	}
	b.segment = s

	atomic.StoreUint64(&b.currentID, b.segment.getMinId())
	b.Unlock()

	logger.Info("[IDG] biz init ok!", zap.Int64("bizid", bizid), zap.Uint64("maxid", s.Maxid))
	return b
}

// getNextSegment 获取下一段ID
func (b *IdBiz) getNextSegment() *IdSegment {
	b.queryMux.Lock()
	defer b.queryMux.Unlock()
	idSegment, err := b.wk.dao.GetBizStampById(b.bizid)
	if err != nil {
		logger.Error("[IDG] getNextSegment error", zap.Error(err))
		return nil
	}
	t := time.Now().UTC()
	idSegment.Updated = t.UnixMilli()
	effrows, e1 := b.wk.dao.UpdateBizStamp(idSegment)
	if e1 != nil {
		logger.Error("[IDG] getNextSegment error on UpdateBizStamp", zap.Error(e1))
		return nil
	}
	if effrows == 1 {
		idSegment.Maxid += idSegment.Idstep
		return idSegment
	}

	return nil
}

// getSectionIds 获取一段ID，自定义一段的长度
func (b *IdBiz) getSectionIds(customStep uint64) uint64 {
	b.queryMux.Lock()
	defer b.queryMux.Unlock()
	idSegment, err := b.wk.dao.GetBizStampById(b.bizid)
	if err != nil {
		logger.Error("[IDG] getSectionIds error", zap.Error(err))
		return 0
	}
	t := time.Now().UTC()
	idSegment.Updated = t.UnixMilli()
	idSegment.Customstep = customStep
	effrows, e1 := b.wk.dao.UpdateBizSectionStamp(idSegment)
	if e1 != nil {
		logger.Error("[IDG] getSectionIds error on UpdateBizSectionStamp", zap.Error(e1))
		return 0
	}
	if effrows == 1 {
		maxid := idSegment.Maxid + customStep
		return maxid
	}

	return 0
}

func (b *IdBiz) getID(bizid int64, step uint64) uint64 {
	if step > 0 {
		return b.getSectionIds(step)
	}
	idend := false

	if b.segment.Maxid <= atomic.LoadUint64(&b.currentID) ||
		b.segment.getMiddleId() == atomic.LoadUint64(&b.currentID) {

		b.Lock()

		// 50% get netxt
		if b.segment.getMiddleId() == atomic.LoadUint64(&b.currentID) {
			// 异步拉取下一段
			go b.asyncUpdateNextSegment()
		}

		// swap
		if b.segment.Maxid <= atomic.LoadUint64(&b.currentID) {
			if b.nextSeg != nil { // next segment prepared
				b.segment = b.nextSeg
				atomic.StoreUint64(&b.currentID, b.segment.getMinId())
				b.nextSeg = nil
			} else {
				// next segment not available
				s := b.getNextSegment()
				if s != nil {
					b.segment = s
					atomic.StoreUint64(&b.currentID, b.segment.getMinId())
				} else {
					// fail
					idend = true
				}
			}
		}
		b.Unlock()
	}

	if idend {
		return 0
	}

	return atomic.AddUint64(&b.currentID, 1)
}

func (b *IdBiz) asyncUpdateNextSegment() {
	if b.nextSeg != nil {
		return
	}
	s := b.getNextSegment()
	if s != nil {
		b.nextSeg = s
	}
}
